
#include "fxcc/core/graph/opengl3/ImGuiGlfwApp.h"
#include "fxcc/core/graph/common/Geometry.h"
#include "fxcc/imgui/Editor.h"

#include "fxcc/core/graph/common/ImageMipmap.h"
#include "fxcc/core/graph/common/sdf/SDFCharMap.h"
#include "fxcc/core/graph/common/sdf/SDFInfo.h"
#include "fxcc/core/graph/common/sdf/SDFGeoData.h"

#include "fxcc/core/AssertManager.h"

#include "fxcc/bin/Serializer.h"

using namespace fxcc::platform;
using namespace fxcc::graph::common;
using namespace fxcc::graph::opengl3;
using namespace fxcc::imgui::graph::opengl3;
using BaseApp = fxcc::imgui::graph::opengl3::GlfwApp;

#undef main

extern "C" {
    __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
}

struct SDFMesh
{
    std::shared_ptr<fxcc::graph::common::MeshJack> m_MeshChars;

};

struct SDFResource :public fxcc::Base
{
    std::string m_DirPath;

    SDFInfo m_SDFInfo;

    SDFCharMap m_SDFCharMap;

    std::vector<std::shared_ptr<fxcc::graph::common::Texture2DJack>> m_SDFTextures;

    SDFMesh CreateTextMesh(const std::wstring& text, const unsigned int fontSize)
    {
        ztclog::info("Font Size Basic %d", m_SDFInfo.m_FontSize);

        SDFMesh sdfMesh;

        SDFGeoData sdfSubData;

        glm::vec2 startPos(0, 0);
        unsigned int indexSep{ 0 };


        for (const wchar_t& ch : text)
        {
            SDFChar sdfChar = m_SDFCharMap.Get(ch);

            SDFGeoData sdfGeoData;
            sdfGeoData.indices32 =
            {
                0, 1, 2,
                0, 2, 3
            };

            for (auto& index : sdfGeoData.indices32)
            {
                index += 4 * indexSep;
            }


            float x = startPos.x;
            float y = startPos.y;
            float scale = (float)fontSize / (float)m_SDFInfo.m_FontSize;
 
            float xpos = x + sdfChar.m_Bearing.x * scale;
            float ypos = -(sdfChar.m_Size.y - sdfChar.m_Bearing.y) * scale;

            float w = sdfChar.m_Size.x * scale;
            float h = sdfChar.m_Size.y * scale;

 
            std::vector<glm::vec2> vertices =
            {
                glm::vec2(xpos, ypos + h),
                glm::vec2(xpos, ypos),
                glm::vec2(xpos + w, ypos),
                glm::vec2(xpos + w, ypos + h)
            };

            sdfGeoData.vertices = vertices;
            startPos.x += (sdfChar.m_Advance.x / 64.0f) * scale;

            glm::vec2 gridSize = glm::vec2(sdfChar.m_TexGridNum);  
            glm::vec2 gridPos = glm::vec2(sdfChar.m_TexGridIndex); 
            float paddingRatio = 0.125f;

            glm::vec2 uvMin = glm::vec2(paddingRatio, paddingRatio);
            glm::vec2 uvMax = glm::vec2(1.0f - paddingRatio, 1.0f - paddingRatio);

            //glm::vec2 uvBearingSize;

            //uvBearingSize.x = sdfChar.m_Bearing.x / sdfChar.m_Size.x;
            //uvBearingSize.y = sdfChar.m_Bearing.y / sdfChar.m_Size.y;

            //uvMin.x += uvBearingSize.x;
            //uvMin.y += uvBearingSize.y;

            std::vector<glm::vec2> baseUVs = {
                 {uvMin.x, uvMin.y},
                 {uvMin.x, uvMax.y},
                 {uvMax.x, uvMax.y},
                 {uvMax.x, uvMin.y}
            };

            for (auto& uv : baseUVs)
            {
                glm::vec2 adjustedUV = uv / gridSize + gridPos / gridSize;
                sdfGeoData.texCoords.push_back(adjustedUV);
            }

            for (int i = 0; i < 4; i++)
            {
                sdfGeoData.pageIndices.push_back(sdfChar.m_PageIndex);
            }
  
            sdfSubData.Add(sdfGeoData);
            
            indexSep++;

        }
        sdfMesh.m_MeshChars = fxcc::graph::common::GraphImpl::CreateSDFMesh(sdfSubData);
        return sdfMesh;
    };
    SDFResource(const std::string& dir) :m_DirPath(dir)
    {
        m_Available = Init();
    };

    void SetShaderResource(int samplerStartSlot = 0)
    {
        for (int i = 0; i < m_SDFTextures.size(); i++)
        {
            m_SDFTextures[i]->SetShader(i);
        }
    }

    bool Init()
    {
        std::string m_SDFInfoPath = (fs::path(m_DirPath) / MSDFConfigName()).string();
        std::string m_SDFCharMapPath = (fs::path(m_DirPath) / CharMapFileName()).string();

        fxcc::yml::Serializer ymlser;
        fxcc::bin::Serializer binser;

        ymlser.DecodePath<SDFInfo>(m_SDFInfoPath, m_SDFInfo);
        binser.ReadPath<SDFCharMap>(m_SDFCharMapPath, m_SDFCharMap);

        std::string bmpSuffix = SDFInfo::SDFPageSuffix();
        for (int pageIndex = 0; pageIndex < m_SDFInfo.m_NumPages; pageIndex++)
        {
            const std::string bmpFilePath = (fs::path(m_DirPath) / (std::to_string(pageIndex))).string() + bmpSuffix;

            fxcc::graph::common::Texture2DJack::Desc texDesc;

            fxcc::graph::common::ImageMipmap::Desc desc;
            desc.m_WICFlipY = false;
            desc.m_wicHdr = false;
            desc.m_wicReq = 3;
            desc.m_wicPath = bmpFilePath;

            texDesc.m_WICConfig = desc;
            texDesc.m_Type = fxcc::graph::common::Texture2DJack::Desc::Type::_WIC_;
            texDesc.m_SampleType = fxcc::graph::common::SamplerType::_sampler_2d_default_;

            auto texture = fxcc::graph::common::GraphImpl::CreateTexture2D(desc);
            m_SDFTextures.push_back(texture);
        }

        return true;
    };

    std::string m_Text;

    static std::string MSDFConfigName()
    {
        return "msdf.config";
    }
    static std::string CharMapFileName()
    {
        return "char.map";
    }
};

struct SDFEffect
{
    std::string m_FragMain;

    std::shared_ptr<fxcc::graph::opengl3::Shader> m_Shader;

    void Begin()
    {
        m_Shader->Use();
    }
    void End()
    {
        m_Shader->UnUse();
    }
    SDFEffect(const std::string& fragMain)
        :m_FragMain(fragMain)
    {
        Init();
       
    }
    static std::string FragTemp()
    {
        std::string fragCodeTemp = R"(
{{{version}}}

{{{uniformPart}}}


in vec2 TexCoords;
in uint PageIndex;

out vec4 FragColor;

uniform float pxRange; 

uniform sampler2D msdf;

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

float screenPxRange() {
    vec2 msdfSize = vec2(textureSize(msdf, 0));
    vec2 unitRange = vec2(pxRange) / msdfSize; 
    vec2 screenTexSize = vec2(1.0) / fwidth(TexCoords) ;
    return max(0.5 * dot(unitRange, screenTexSize), 1.0);
}

{{{fragMain}}}

void main()
{
    vec3 msd = texture(msdf, TexCoords).rgb;
    float sd = median(msd.r, msd.g, msd.b);
    float screenPxDistance = screenPxRange()*(sd - 0.5);
    float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
    FragColor = Galculate_SDF(opacity);

}
)";
        return fragCodeTemp;
    }
    std::string GetFragCode() {
        
        const auto& buffer = fxcc::graph::opengl3::IBuffers::Inst();
        
        kainjow::mustache::mustache m1 = FragTemp();
        kainjow::mustache::data d1;
        d1.set("version", fxcc::graph::opengl3::IBuffers::GetVersionStr());
        d1.set("uniformPart", fxcc::graph::opengl3::IBuffers::GetUniformPart3d());
        d1.set("fragMain", m_FragMain);

        return m1.render(d1);
    }
    bool Init()
    {
        std::string vertCode = GetVertCode();
        std::string fragCode = GetFragCode();

        std::cout << fragCode << std::endl;

        m_Shader = std::make_shared<fxcc::graph::opengl3::Shader>(vertCode, fragCode);

        m_Shader->Use();
        m_Shader->setInt("msdf", 0);
        m_Shader->Bind("PassBuffer", 0);
        m_Shader->Bind("ObjBuffer", 1);
        //for (int i = 0; i < 8; i++)
        //{
        //    std::string label = std::string("msdf[") + std::to_string(i) + "]";
        //    m_Shader->setInt(label, i);
        //}
        return true;
    }

    std::string GetVertCode()
    {
        auto& buffers = fxcc::graph::opengl3::IBuffers::Inst();

        kainjow::mustache::mustache m1 = GetVertTemp();
        kainjow::mustache::data d1;
        d1.set("version", buffers.GetVersionStr());
        d1.set("uniformPart", buffers.GetUniformPart3d());

        return m1.render(d1);
    }
    std::string GetVertTemp()
    {
        return R"(

{{{version}}}

layout(location=0) in vec2 aPos;
layout(location=1) in uint aPageIndex;
layout(location=2) in vec2 aTexCoord;

{{{uniformPart}}}

out vec2 TexCoords;
out uint PageIndex;

void main()
{
    TexCoords= aTexCoord;
    gl_Position =passData.projView* objData.world*  vec4(aPos,0.0f,1.0f);
};


)";
        
    }
    
};
struct SimpleApp : public BaseApp
{
public:

    OrthoCamera camera2;

    fxcc::graph::common::MaterialView m_MaterialView;

    std::shared_ptr<ImageMipmap> m_ImageMipMap;

    glm::vec4 color;

    fxcc::graph::common::Camera camera;

    float pxRange{ 4.0f };

    std::shared_ptr<SDFEffect> m_SDFEffect;

    std::shared_ptr<SDFResource> m_SDFResource;

    std::string m_Text;

    SDFMesh m_SDFMesh;

    SimpleApp(const fxcc::platform::common::App::Desc& desc) : BaseApp(desc), color(1.0)
    {
    }
    bool Init() override
    {
        if (!BaseApp::Init())
        {
            return false;
        }

        if (!InitResources())
        {
            return false;
        }

        return true;
    };

    //std::shared_ptr<fxcc::graph::opengl3::MeshJack> m_MeshJack;

    bool InitResources()
    {
        ImageMipmap::MipMapData image(1024, 1024, 4, 2);
        fxcc::graph::opengl3::RenderTargetJack::Desc desc(1, image);

        camera.SetPosition(glm::vec3(20.0f));
        camera.LookAt(glm::vec3(0.0f));

        std::string _p1 = fxcc::AssertManager::GetRelativeFile("sdf_out2/arial");
        m_SDFResource = std::make_shared<SDFResource>(_p1);

        const std::string fragCode = R"(

uniform vec4 bgColor;
uniform vec4 fgColor;

vec4 Galculate_SDF(float opacity)
{
    return mix( bgColor, fgColor,opacity);
};

)";
        m_SDFEffect = std::make_shared<SDFEffect>(fragCode);

        m_SDFMesh = m_SDFResource->CreateTextMesh(L"Hello World My Name is fbz", 25);

        camera2.SetPosition(glm::vec3(0, 0, 20));
        camera2.LookAt(glm::vec3(.0f));
        camera2.left = 0.0f;
        camera2.right = m_Desc.m_Size.x;
        camera2.bottom = 0;
        camera2.top = m_Desc.m_Size.y;
        camera2.zNear = -20.0f;
        camera2.zFar = 20.0f;
        return true;
    };


    fxcc::imgui::Editor editor;

    void OnEdit() override
    {
        ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());

        ImGui::Begin("SDFs");
        for (auto tex : m_SDFResource->m_SDFTextures)
        {
            ImGui::Image(tex->GetTextureId(), { 50,50 });
        }
        ImGui::End();

        ImGui::Begin("SDF ");
        editor.EditInst<fxcc::graph::common::OrthoCamera>("Camera", camera2);
        editor.EditInst<fxcc::graph::common::Transform>("transform", transform);
        editor.EditInst<float>("range", pxRange);

        ImGui::End();
        
        ImGui::Begin("Framebuffer");
        editor.AddFrameBuffer(m_RTJack->m_Textures[0]->GetTextureId());
        ImGui::End();
    }
    fxcc::graph::common::Transform transform;
    std::shared_ptr<fxcc::graph::opengl3::RenderTargetJack> m_RTJack;

    void OnDestroy() override
    {

        BaseApp::OnDestroy();
    }
    void OnFrameRender() override
    {
        glViewport(0, 0, m_Desc.m_Size.x, m_Desc.m_Size.y);

        glClearColor(color.x, color.y, color.z, color.w);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        auto& buffers = fxcc::graph::opengl3::IBuffers::Inst();

        m_RTJack->Begin();
        m_RTJack->ClearColor(color);
        m_RTJack->ClearBuffer(1, 1);

        m_SDFEffect->Begin();

        fxcc::graph::common::Effect3d::PassData passData;

        passData.Load2(camera2);
        
        buffers.SetPassData(passData);

        m_SDFResource->SetShaderResource(0);

        fxcc::graph::common::Effect3d::ObjData objData;
        objData.Load(transform);
        buffers.SetObjData(objData);

        m_SDFEffect->m_Shader->setFloat("pxRange", pxRange);
        m_SDFEffect->m_Shader->setVec4("bgColor", { 1.0f,1.0f,1.0f,1.0f });
        m_SDFEffect->m_Shader->setVec4("fgColor", { 0.0f,0.0f,0.0f,1.0f });
        
        m_SDFMesh.m_MeshChars->Bind();
        m_SDFMesh.m_MeshChars->DrawElementsAuto();
        m_SDFEffect->End();

        m_RTJack->End();
    };

    void OnFramebuffersize(int w, int h) override
    {
        BaseApp::OnFramebuffersize(w, h);
        camera.SetAspect(m_Desc.Aspect());
        fxcc::graph::opengl3::RenderTargetJack::Desc desc(1, { w, h, 3, 0 });
        m_RTJack = std::make_shared<fxcc::graph::opengl3::RenderTargetJack>(desc);


    }
    void OnUpdate() override
    {
        BaseApp::OnUpdate();

        float ctTime = m_GlfwlTimer.m_CurrentTime;
        float deltaTime = m_GlfwlTimer.m_DeltaTime;

        //camera2.FreeMove(m_Input, deltaTime);

    }
};

int main()
{
    ztclog::pushFunction([](const ztclog::Line& line)
        {
            if (line.m_Type != ztclog::Line::Type::_line_debug_)
            {
                std::cout << line.m_Text << std::endl;
            } });

            using _App = SimpleApp;
            using _Desc = fxcc::platform::common::App::Desc;

            _Desc desc;
            desc.m_Pos = glm::ivec2(100, 100);
            desc.m_Size = glm::ivec2(900, 600);
            desc.m_ScaleFactor = 1.0f;

            desc.m_Title = "sdh render ";
            std::shared_ptr<_App> _app = std::make_shared<_App>(desc);
            return _app->Run();
};
